package commons;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

/**
 * 粒子群优化算法
 *
 * @author Ringo
 * @date 2021/10/10 17:19
 */
public class ParticleSwarmOptimization {

    /**
     * 全局最优位置
     */
    private static double[] gBest;

    /**
     * 全局最优位置对应的 fitness（适应度）
     */
    private static double gBestFitness = Double.MAX_VALUE;

    /**
     * 粒子数
     */
    private static int particleNum = 20;

    /**
     * 迭代次数
     */
    private static int N = 500;

    /**
     * 学习因子/加速度常数
     * <p>
     * c1: 个体经验
     * c2: 群体经验
     */
    private static int c1 = 2, c2 = 2;

    /**
     * 惯性因子: 决定算法全局和局部寻优能力的强弱
     */
    private static double w = 1.4;

    /**
     * 粒子群
     */
    private static List<Particle> particles = new ArrayList<>(particleNum);

    /**
     * 适应度列表
     */
    private static List<Double> fitnessList = new ArrayList<>(N);

    private static Random random = new Random();

    // =================================================================================
    // 算法主要逻辑

    /**
     * PSO算法执行过程
     */
    public static void process() {
        int n = 0;
        initParticles();
        updateGBest();
        while (n++ < N) {
            updateV();
            updateX();
            updateGBest();
            fitnessList.add(gBestFitness);
            System.out.println("迭代次数 = " + n + "|\t" +
                    "当前gBest(全局最优位置) = " + Arrays.toString(gBest) + "|\t"
                    + "gBestFitness(全局最优适应度) = " + gBestFitness);
        }
    }


    // ==================================================================================
    // methods

    /**
     * 更新 gBest（全局最优位置）
     */
    @SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
    public static void updateGBest() {
        // 粒子群中最小的适应值
        double minFitness = Double.MAX_VALUE;
        // 最小适应值粒子在粒子群中的下标
        int index = 0;

        // 找到粒子群中适应值最小的粒子
        for (int i = 0; i < particleNum; i++) {
            if (particles.get(i).fitness < minFitness) {
                index = i;
                minFitness = particles.get(i).fitness;
            }
        }

        // 个体适应值 < 全局适应值, 则更新 gBest（全局最优值） 和 gBestFitness（全局最优适应值）
        if (minFitness < gBestFitness) {
            gBestFitness = minFitness;
            double[] candidate = particles.get(index).pBest;
            gBest = Arrays.copyOf(candidate, candidate.length);
        }
    }

    /**
     * @return 返回一个 0 ~ 1 的随机数
     */
    public static double rand() {
        return random.nextDouble();
    }

    /**
     * 初始化所有粒子并加入到粒子群
     */
    public static void initParticles() {
        for (int i = 0; i < particleNum; i++) {
            Particle particle = new Particle();
            particle.initX();
            particle.initV();
            particle.fitness = particle.calculateFitness();
            particles.add(particle);
        }
    }

    /**
     * 更新每个粒子的速度
     */
    public static void updateV() {
        for (Particle particle : particles) {
            for (int i = 0; i < particle.dimension; i++) {
                double v = w * particle.v[i] +
                        c1 * rand() * (particle.pBest[i] - particle.x[i]) +
                        c2 * rand() * (gBest[i] - particle.x[i]);
                if (v > particle.vMax) {
                    v = particle.vMax;
                } else if (v < -particle.vMax) {
                    v = -particle.vMax;
                }
                particle.v[i] = v;
            }
        }
    }

    /**
     * 更新每个粒子的位置和 pBest（局部最优位置）
     */
    public static void updateX() {
        for (Particle particle : particles) {
            for (int i = 0; i < particle.dimension; i++) {
                particle.x[i] += particle.v[i];
            }
            double newFitness = particle.calculateFitness();
            // 新的 fitness 比原来小, 则更新粒子的 fitness 和 pBest（局部最优位置）
            if (newFitness < particle.fitness) {
                particle.fitness = newFitness;
                particle.pBest = Arrays.copyOf(particle.x, particle.x.length);
            }
        }
    }

    public static void main(String[] args) {
        process();
    }


    /**
     * 粒子类
     */
    public static class Particle {
        /**
         * 维度
         */
        public int dimension = 2;

        /**
         * 粒子当前的位置
         */
        public double[] x = new double[dimension];

        /**
         * 局部最优的位置
         */
        public double[] pBest = new double[dimension];

        /**
         * 粒子的速度
         */
        public double[] v = new double[dimension];

        /**
         * 粒子最大速度（标量）
         */
        public double vMax = 2d;

        /**
         * 适应度
         */
        public double fitness;

        Random random = new Random();

        /**
         * 根据当前位置计算适应值
         *
         * @return 适应值
         */
        public double calculateFitness() {
            return 100 * (Math.pow((x[1] - x[0] * x[0]), 2)) + Math.pow((x[0] - 1), 2);
        }

        /**
         * 初始化位置 和 pBest（局部最优位置）
         */
        public void initX() {
            for (int i = 0; i < dimension; i++) {
                // nextInt(int) => 取 [0, n) 之间的随机 int 值, 左闭右开
                x[i] = random.nextInt(50);
                pBest[i] = x[i];
            }
        }

        /**
         * 初始化自己的速度
         */
        public void initV() {
            for (int i = 0; i < dimension; i++) {
                // nextDouble(): 随机产生一个 0~1 的随机小数
                // 速度取 [-2, 2] 之前, 矢量
                v[i] = random.nextDouble() * 4 - 2;
            }
        }
    }
}
